﻿

using Cordyceps.Elasticsearch.Tool.Exceptions;
using Cordyceps.Elasticsearch.Tool.Options;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Transport;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Cordyceps.Elasticsearch.Tool.Services;

/// <summary>
/// Elasticsearch服务实现
/// </summary>
public class ElasticsearchService : IElasticsearchService, IDisposable
{
    private readonly ElasticsearchClient _client;
    private readonly ILogger<ElasticsearchService> _logger;
    private readonly ElasticsearchOptions _options;
    private bool _disposed = false;

    /// <summary>
    /// 初始化Elasticsearch服务
    /// </summary>
    /// <param name="options">配置选项</param>
    /// <param name="logger">日志记录器</param>
    public ElasticsearchService(IOptions<ElasticsearchOptions> options, ILogger<ElasticsearchService> logger)
    {
        _options = options.Value;
        _logger = logger;
        
        // 验证配置
        if (_options.Nodes == null || _options.Nodes.Length == 0)
        {
            throw new ArgumentException("Elasticsearch nodes must be configured");
        }

        // 创建连接设置
        var settings = new ElasticsearchClientSettings(new Uri(_options.Nodes[0]))
            .RequestTimeout(TimeSpan.FromSeconds(_options.RequestTimeoutSeconds))
            .MaximumRetries(_options.MaxRetries)
            .EnableDebugMode(_options.EnableDebugMode ? _logger : null);

        // 配置多个节点（如果有）
        if (_options.Nodes.Length > 1)
        {
            var nodes = _options.Nodes.Select(node => new Uri(node)).ToArray();
            settings = settings.ConnectionPool(new SniffingConnectionPool(nodes));
        }

        // 配置认证（如果提供了用户名和密码）
        if (!string.IsNullOrEmpty(_options.Username) && !string.IsNullOrEmpty(_options.Password))
        {
            settings = settings.Authentication(new BasicAuthentication(_options.Username, _options.Password));
        }

        // 配置证书指纹（如果提供了）
        if (!string.IsNullOrEmpty(_options.CertificateFingerprint))
        {
            settings = settings.CertificateFingerprint(_options.CertificateFingerprint);
        }

        // 配置SSL验证（如果需要禁用）
        if (_options.DisableSslVerification)
        {
            settings = settings.ServerCertificateValidationCallback(
                (cert, chain, errors) => true); // 接受所有证书
        }

        // 创建客户端
        _client = new ElasticsearchClient(settings);
        
        _logger.LogInformation("Elasticsearch client initialized with {NodeCount} nodes", _options.Nodes.Length);
    }

    /// <summary>
    /// 获取Elasticsearch客户端实例
    /// </summary>
    public ElasticsearchClient Client => _client;

    /// <inheritdoc />
    public async Task<IndexResponse> IndexDocumentAsync<T>(T document, string? indexName = null, CancellationToken cancellationToken = default) where T : class
    {
        var targetIndex = GetTargetIndex(indexName);
        
        try
        {
            var response = await _client.IndexAsync(document, idx => idx.Index(targetIndex), cancellationToken);
            CheckResponse(response, "IndexDocument", targetIndex);
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to index document to index {IndexName}", targetIndex);
            throw new ElasticsearchException($"Failed to index document to index {targetIndex}", ex)
            {
                Operation = "IndexDocument",
                IndexName = targetIndex
            };
        }
    }

    /// <inheritdoc />
    public async Task<BulkResponse> BulkIndexDocumentsAsync<T>(IEnumerable<T> documents, string? indexName = null, CancellationToken cancellationToken = default) where T : class
    {
        var targetIndex = GetTargetIndex(indexName);
        
        try
        {
            var response = await _client.BulkAsync(b => b
                .Index(targetIndex)
                .IndexMany(documents), cancellationToken);
            
            CheckResponse(response, "BulkIndex", targetIndex);
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to bulk index documents to index {IndexName}", targetIndex);
            throw new ElasticsearchException($"Failed to bulk index documents to index {targetIndex}", ex)
            {
                Operation = "BulkIndex",
                IndexName = targetIndex
            };
        }
    }

    /// <inheritdoc />
    public async Task<T?> GetDocumentAsync<T>(string id, string? indexName = null, CancellationToken cancellationToken = default) where T : class
    {
        var targetIndex = GetTargetIndex(indexName);
        
        try
        {
            var response = await _client.GetAsync<T>(id, idx => idx.Index(targetIndex), cancellationToken);
            
            if (!response.IsValidResponse)
            {
                if (response.ApiCall.HttpStatusCode == 404)
                {
                    // 文档不存在是正常情况，不是异常
                    return null;
                }
                
                CheckResponse(response, "GetDocument", targetIndex, id);
            }
            
            return response.Source;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to get document {DocumentId} from index {IndexName}", id, targetIndex);
            throw new ElasticsearchException($"Failed to get document {id} from index {targetIndex}", ex)
            {
                Operation = "GetDocument",
                IndexName = targetIndex,
                DocumentId = id
            };
        }
    }

    /// <inheritdoc />
    public async Task<SearchResponse<T>> SearchAsync<T>(Action<SearchRequestDescriptor<T>> searchDescriptor, string? indexName = null, CancellationToken cancellationToken = default) where T : class
    {
        var targetIndex = GetTargetIndex(indexName);
        
        try
        {
            var response = await _client.SearchAsync<T>(s =>
            {
                s.Index(targetIndex);
                searchDescriptor(s);
            }, cancellationToken);
            
            CheckResponse(response, "Search", targetIndex);
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to search documents in index {IndexName}", targetIndex);
            throw new ElasticsearchException($"Failed to search documents in index {targetIndex}", ex)
            {
                Operation = "Search",
                IndexName = targetIndex
            };
        }
    }

    /// <inheritdoc />
    public async Task<DeleteResponse> DeleteDocumentAsync<T>(string id, string? indexName = null, CancellationToken cancellationToken = default) where T : class
    {
        var targetIndex = GetTargetIndex(indexName);
        
        try
        {
            var response = await _client.DeleteAsync<T>(id, idx => idx.Index(targetIndex), cancellationToken);
            CheckResponse(response, "DeleteDocument", targetIndex, id);
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to delete document {DocumentId} from index {IndexName}", id, targetIndex);
            throw new ElasticsearchException($"Failed to delete document {id} from index {targetIndex}", ex)
            {
                Operation = "DeleteDocument",
                IndexName = targetIndex,
                DocumentId = id
            };
        }
    }

    /// <inheritdoc />
    public async Task<CreateIndexResponse> CreateIndexAsync<T>(IndexDefinition<T> indexDefinition, CancellationToken cancellationToken = default) where T : class
    {
        try
        {
            var response = await _client.Indices.CreateAsync(indexDefinition.IndexName, descriptor =>
            {
                if (indexDefinition.Settings != null)
                {
                    descriptor.Settings(s => s
                        .NumberOfShards(indexDefinition.NumberOfShards)
                        .NumberOfReplicas(indexDefinition.NumberOfReplicas)
                        .MergeWith(indexDefinition.Settings));
                }
                else
                {
                    descriptor.Settings(s => s
                        .NumberOfShards(indexDefinition.NumberOfShards)
                        .NumberOfReplicas(indexDefinition.NumberOfReplicas));
                }

                if (indexDefinition.Mapping != null)
                {
                    descriptor.Mappings(m => m
                        .Properties<T>(p => p.MergeWith(indexDefinition.Mapping.Properties)));
                }

                if (indexDefinition.Aliases != null && indexDefinition.Aliases.Any())
                {
                    descriptor.Aliases(a =>
                    {
                        foreach (var alias in indexDefinition.Aliases)
                        {
                            a.Alias(alias);
                        }
                    });
                }

                return descriptor;
            }, cancellationToken);

            CheckResponse(response, "CreateIndex", indexDefinition.IndexName);
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to create index {IndexName}", indexDefinition.IndexName);
            throw new ElasticsearchException($"Failed to create index {indexDefinition.IndexName}", ex)
            {
                Operation = "CreateIndex",
                IndexName = indexDefinition.IndexName
            };
        }
    }

    /// <inheritdoc />
    public async Task<DeleteIndexResponse> DeleteIndexAsync(string indexName, CancellationToken cancellationToken = default)
    {
        try
        {
            var response = await _client.Indices.DeleteAsync(indexName, cancellationToken);
            CheckResponse(response, "DeleteIndex", indexName);
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to delete index {IndexName}", indexName);
            throw new ElasticsearchException($"Failed to delete index {indexName}", ex)
            {
                Operation = "DeleteIndex",
                IndexName = indexName
            };
        }
    }

    /// <inheritdoc />
    public async Task<bool> IndexExistsAsync(string indexName, CancellationToken cancellationToken = default)
    {
        try
        {
            var response = await _client.Indices.ExistsAsync(indexName, cancellationToken);
            return response.Exists;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to check if index {IndexName} exists", indexName);
            throw new ElasticsearchException($"Failed to check if index {indexName} exists", ex)
            {
                Operation = "IndexExists",
                IndexName = indexName
            };
        }
    }

    /// <summary>
    /// 检查响应是否有效，如果无效则抛出异常
    /// </summary>
    private void CheckResponse(ElasticsearchResponse response, string operation, string indexName, string? documentId = null)
    {
        if (!response.IsValidResponse)
        {
            var errorMessage = $"Elasticsearch {operation} operation failed for index {indexName}";
            if (!string.IsNullOrEmpty(documentId))
            {
                errorMessage += $", document {documentId}";
            }
            
            errorMessage += $". Details: {response.DebugInformation}";
            
            _logger.LogError("Elasticsearch operation failed: {DebugInformation}", response.DebugInformation);
            throw new ElasticsearchException(errorMessage)
            {
                Operation = operation,
                IndexName = indexName,
                DocumentId = documentId
            };
        }
    }

    /// <summary>
    /// 获取目标索引名称，如果未指定则使用默认索引
    /// </summary>
    private string GetTargetIndex(string? indexName)
    {
        if (!string.IsNullOrEmpty(indexName))
        {
            return indexName;
        }
        
        if (!string.IsNullOrEmpty(_options.DefaultIndex))
        {
            return _options.DefaultIndex;
        }
        
        throw new ArgumentException("Index name must be specified when no default index is configured");
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // 这里可以释放客户端资源，但ElasticsearchClient没有Dispose方法
                // 如果需要，可以添加其他需要释放的资源
            }
            
            _disposed = true;
        }
    }
}